#pragma once

#include "PLObject.h"
#include "PLEnums.h"
#include "PLLinearEquationSystem.h"

#include <string>
#include <vector>

/**
  *	A PLNonLinearLeastSquaresSolverOptions object capsules all the options
  * needed for a PLNonLinearLeastSquaresSolver object.
  */
class PLNonLinearLeastSquaresSolverOptions
{
public:

	/**
	  *	Constructs a new PLNonLinearLeastSquaresSolverOptions object with
	  * the given arguments
	  *
	  * @param minResidual minimal residual, which is the minimal difference between A * x~ (with x~ as the calculated solution) and A * x (with x as the exact solution)
	  * @param maxIterations maximum number of iterations
	  * @param maxLineSearchSteps maximum number of line search steps, which is the number of steps used to find the best gradient length after a single iteration
	  * @param minGradientAbsComponent smallest absolute value, that is tolerated, when the the biggest absolute value of the gradient is figured out
	  */
	PLNonLinearLeastSquaresSolverOptions(
		double minResidual = 0.0, PLuint maxIterations = 100,
		PLuint maxLineSearchSteps = 100, double minGradientAbsComponent = 1.0e-12 )
	{
		mMinResidual = minResidual;
		mMaxIterations = maxIterations;
		mMaxLineSearchSteps = maxLineSearchSteps;
		mMinGradientAbsComponent = minGradientAbsComponent;
	}

	/** minimal difference between A * x~ (with x~ as the calculated solution) and A * x (with x as the exact solution) */
	double mMinResidual;
	/** maximum number of iterations */
	PLuint mMaxIterations;
	/** maximum number of line search steps, which is the number of steps used to find the best gradient length after a single iteration */
	PLuint mMaxLineSearchSteps;
	/** smallest absolute value, that is tolerated, when the the biggest absolute value of the gradient is figured out */
	double mMinGradientAbsComponent;
};

/**
  *	PLNonLinearLeastSquaresSolver is the base class for a solver of a non
  * linear equation system
  */
class PLNonLinearLeastSquaresSolver
{
public:
	PLNonLinearLeastSquaresSolver( const PLNonLinearLeastSquaresSolverOptions &basicOptions);
	~PLNonLinearLeastSquaresSolver( void );
	const PLuint &GetNumberOfVariables( void ) const;
	void Solve( void );
	virtual std::string GetCurrentChannelName( void ) const = 0;
	virtual double Residual( void ) const = 0;
	virtual double FirstTermResidual( void ) const = 0;
	virtual double SecondTermResidual( void ) const = 0;

protected:
	/** Number of variables in the non linear equation system */
	PLuint mNumberOfVariables;

	virtual void BackupVariables( std::vector<double> *backup ) = 0;
	virtual void BuildLinearEquationSystem( void ) = 0;
	virtual void FinalizeSolver( void ) = 0;
	virtual void Initialize( void );
	virtual void RestoreVariables( const std::vector<double> &backup ) = 0;
	virtual void UpdateVariables( const std::vector<double> &gradient ) = 0;
	
	void FillJTJ( const std::vector<PLuint> &variables,
		const std::vector<double> &coefficients,
		const double &weight, const double &f );

private:
	/** Basic options needed by a PLNonLinearLeastSquaresSolver object */
	PLNonLinearLeastSquaresSolverOptions mBasicOptions;

	/** Linear equation system used to solve the locally approximated linear problem */
	PLLinearEquationSystem *mLinearEquationSystem;

	void Reset( void );
};